package domain;

import java.beans.Transient;
import java.util.*;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

import controllers.MyObserver;
import controllers.MySubject;

@XmlRootElement(name = "tournament")
public class Tournament implements MySubject{
	
	@XmlElement(name = "date")
	private Date date;
	@XmlElement(name = "tournamentName")
	private String name;
	@XmlTransient
	private List<Player> playersToBePaired = new ArrayList<Player>();
	@XmlTransient
	private List<Player> registeredPlayers = new ArrayList<Player>();
	@XmlElement(name = "gameID")
	private int gameID;
	@XmlTransient
	private List<Game> games = new ArrayList<Game>();
	
	@XmlTransient
	private List<MyObserver> observers = new ArrayList<MyObserver>();
	
	/**
	 * @param minimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther
	 * First condition of V1 Tournament.pairUp(..) method
	 * -> The minimum amount of games ago the potential paired up players have played against each other
	 * 
	 */
	private static int minimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther;
	/**
	 * @param maximumAmountOfPlaces2PlayersApartOfEachOther
	 * Second condition of V1 Tournament.pairUp(..) method
	 * -> The maximum amount of places the potential paired up players are apart of each other
	 */
	private static int maximumAmountOfPlaces2PlayersApartOfEachOther = 4; //testwaarde pairup, moet configureerbaar zijn

	/**
	 * 
	 * @param name
	 * Unique name of the new tournament
	 * 
	 */
	public Tournament(String name) {
		setName(name);
		setDate(new Date());
	}

	//TODO + this argument probably OBSOLETE
	public List<Game> pairUp(String[][] playersToBePaired) {
		List<Game> formedGames = new ArrayList<Game>();
		Game formedGame;
		// The mapping of Strings (names of players) -> Player objects
		// NOT NECESSARY ANYMORE : playersToBePaired already set by selecting/shuttling...
		/*for(String[] playerToBePairedS : playersToBePaired)
		{
			Player playerToBePaired = findPlayerToBePairedByName(playerToBePairedS);
			this.playersToBePaired.add(playerToBePaired);
		}*/
		// The players for pairing sorted as if in the real ranking
		Collections.sort(this.playersToBePaired, new PlayerComparator());
		System.out.println("Players to be paired SORTED: " + this.playersToBePaired);
		// The full ranking needed to calculate places between potential pair of players
		List<Player> playersAccordingToRanking = getPlayersAccordingToRanking();

		// Iterator that starts at last spot of playersToBePaired List(= player ranked lowest)
		ListIterator<Player> playersToBePairedIterator = this.playersToBePaired.listIterator(this.playersToBePaired.size());
		// Iterator that starts at second last spot of playersToBePaired List(= player ranked higher than lowest)
		ListIterator<Player> playersToBePairedIterator2 = this.playersToBePaired.listIterator(this.playersToBePaired.size() - 1);
		
		int placeLowestPlayerInRanking, placeHigherPlayerInRanking;
		while(playersToBePairedIterator.hasPrevious() && playersToBePairedIterator.previousIndex() >= 1)
		{
			Player lowestPlayer = playersToBePairedIterator.previous();
			placeLowestPlayerInRanking = playersAccordingToRanking.indexOf(lowestPlayer) + 1;
			boolean foundOne = false;
			while(playersToBePairedIterator2.hasPrevious() && !foundOne && playersToBePairedIterator2.previousIndex() >= 0)
			{
				foundOne = false;
				Player higherPlayer = playersToBePairedIterator2.previous();
				if(!(lowestPlayer.isNewPlayer() || higherPlayer.isNewPlayer()))
				{
					placeHigherPlayerInRanking = playersAccordingToRanking.indexOf(higherPlayer) + 1;
					// Integer.MIN_VALUE represents the value if the players never played against each other
					if(lowestPlayer.howManyGamesAgoLastPlayedAgainstPlayer(higherPlayer) != Integer.MIN_VALUE)
					{
						// Check first condition (minimum amount of games ago 2 players last played against each other) V1 pairUp(..) meth
						if(checkMinimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther(lowestPlayer, higherPlayer))
						{
							// Check second condition (maximum amount of places 2 players apart of each other) V1 pairUp(..) meth
							if(Tournament.maximumAmountOfPlaces2PlayersApartOfEachOther >= (placeHigherPlayerInRanking - placeLowestPlayerInRanking))
							{
								formedGame = formGame(lowestPlayer, higherPlayer);
								this.games.add(formedGame);
								formedGames.add(formedGame);
								foundOne = true;
							}
						}
					}
					// Players never played against each other => the condition (amount of games ago) doesn't have to be checked and can be skipped
					else
					{
						// Check second condition (maximum amount of places 2 players apart of each other) V1 pairUp(..) meth
						if(Tournament.maximumAmountOfPlaces2PlayersApartOfEachOther >= (placeLowestPlayerInRanking - placeHigherPlayerInRanking))
						{
							formedGame = formGame(lowestPlayer, higherPlayer);
							this.games.add(formedGame);
							formedGames.add(formedGame);
							foundOne = true;
						}
					}
				}
				else
				{
					formedGame = formGame(lowestPlayer, higherPlayer);
					this.games.add(formedGame);
					formedGames.add(formedGame);
					foundOne = true;
				}
				if(foundOne)
				{
					this.playersToBePaired.remove(lowestPlayer); this.playersToBePaired.remove(higherPlayer);
					// Reset starting position of shortened playersToBePaired List
					playersToBePairedIterator = this.playersToBePaired.listIterator(this.playersToBePaired.size());
					if(this.playersToBePaired.size() >= 2)
					{
						playersToBePairedIterator2 = this.playersToBePaired.listIterator(this.playersToBePaired.size() - 1);
					}
				}
			}
		}
		notifyObservers("RegisteredPlayersTableModel");
		notifyObservers("GamesTableModel");
		return formedGames;
	}
	
	/**
	 * 
	 * @return
	 * A list of games formed, by respecting 2 conditions
	 * 1/ The players have a minimum amount of games (configured) where they haven't faced off against each other
	 * 2/ The players' ranking isn't bigger than the maximum amount of places configured
	 * 
	 * More documentation in the .doc file 'Paringsprogramma voor de scholen' paragraph 'Paringen'
	 */
	public List<Game> pairUp() {
		if(this.playersToBePaired.isEmpty())
			return null;
		List<Game> formedGames = new ArrayList<Game>();
		Game formedGame;
		
		// The mapping of Strings (names of players) -> Player objects
		// NOT NECESSARY ANYMORE : playersToBePaired already set by selecting/shuttling...
		/*for(String[] playerToBePairedS : playersToBePaired)
		{
			Player playerToBePaired = findPlayerToBePairedByName(playerToBePairedS);
			this.playersToBePaired.add(playerToBePaired);
		}*/
		
		// A copy of the players for pairing to search through 
		//(start descending from starting point, if needed continue from top/end of list to starting point => full search)
		// Provides an element of randomness in the pairup of players
		List<Player> copyPlayersToBePaired = new ArrayList<Player>(this.playersToBePaired);
		// The players for pairing sorted as if in the real ranking
		Collections.sort(this.playersToBePaired, new PlayerComparator());
		
		//DEBUG
		System.out.println("Players to be paired SORTED: " + this.playersToBePaired);
		
		// The full ranking needed to calculate places between potential pair of players
		List<Player> playersAccordingToRanking = getPlayersAccordingToRanking();

		// Iterator that starts at last spot of playersToBePaired List(= player ranked lowest)
		ListIterator<Player> playersToBePairedIterator = this.playersToBePaired.listIterator(this.playersToBePaired.size());
		// The index to indicate the starting point in the copy collection of players
		int starterIndexOfSearch = copyPlayersToBePaired.indexOf(playersToBePairedIterator.previous());
		// Iterator that starts at second last spot of playersToBePaired List(= player ranked higher than lowest)
		ListIterator<Player> playersToBePairedIterator2 = copyPlayersToBePaired.listIterator(starterIndexOfSearch);
		Player lowestPlayer = playersToBePairedIterator.next();
		Player potentialAdversary;
		int placeLowestPlayerInRanking, placeHigherPlayerInRanking;
		
		// Go through players for pairing sorted by rank (start with lowest ranked, second lowest ranked, etc.)
		//BUG : stopvoorwaarde mss nog nie volledig op punt
		while(playersToBePairedIterator.hasPrevious() && playersToBePairedIterator.previousIndex() >= 0)
		{
			placeLowestPlayerInRanking = playersAccordingToRanking.indexOf(lowestPlayer) + 1;
			boolean foundOne = false;
			// Find a match for lowest ranked player (, second lowest, etc.) by checking the selected players for pairing
			// starting with the player 1 spot above current player (lowest) in the selection (not rankingwise)
			// => This introduces an element of randomness to avoid the same pairup and monotone games
			// !MORE GENERAL APPROACH; Can be configured so it results in games only between players 1 place in the ranking above or below
			while(playersToBePairedIterator2.hasPrevious() && !foundOne && playersToBePairedIterator2.previousIndex() >= -1 && starterIndexOfSearch != playersToBePairedIterator2.previousIndex())
			{
				foundOne = false;
				potentialAdversary = playersToBePairedIterator2.previous();
				if(playersToBePairedIterator2.previousIndex() == -1)
				{
					playersToBePairedIterator2 = copyPlayersToBePaired.listIterator(this.playersToBePaired.size());
				}
				if(!(lowestPlayer.isNewPlayer() || potentialAdversary.isNewPlayer()))
				{
					placeHigherPlayerInRanking = playersAccordingToRanking.indexOf(potentialAdversary) + 1;
					// Integer.MIN_VALUE represents the value if the players never played against each other
					if(lowestPlayer.howManyGamesAgoLastPlayedAgainstPlayer(potentialAdversary) != Integer.MIN_VALUE)
					{
						// Check first condition (minimum amount of games ago 2 players last played against each other) V1 pairUp(..) meth
						if(checkMinimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther(lowestPlayer, potentialAdversary))
						{
							// Check second condition (maximum amount of places 2 players apart of each other) V1 pairUp(..) meth
							if(Tournament.maximumAmountOfPlaces2PlayersApartOfEachOther >= (placeHigherPlayerInRanking - placeLowestPlayerInRanking))
							{
								formedGame = formGame(lowestPlayer, potentialAdversary);
								this.games.add(formedGame);
								formedGames.add(formedGame);
								foundOne = true;
							}
						}
					}
					// Players never played against each other => the condition (amount of games ago) doesn't have to be checked and can be skipped
					else
					{
						// Check second condition (maximum amount of places 2 players apart of each other) V1 pairUp(..) meth
						if(Tournament.maximumAmountOfPlaces2PlayersApartOfEachOther >= (placeLowestPlayerInRanking - placeHigherPlayerInRanking))
						{
							formedGame = formGame(lowestPlayer, potentialAdversary);
							this.games.add(formedGame);
							formedGames.add(formedGame);
							foundOne = true;
						}
					}
				}
				// There is a new player involved
				// Configured that no rules have to be checked because doesn't have the parameters/values yet
				else
				{
					formedGame = formGame(lowestPlayer, potentialAdversary);
					this.games.add(formedGame);
					formedGames.add(formedGame);
					foundOne = true;
				}
				if(foundOne)
				{
					this.playersToBePaired.remove(lowestPlayer); this.playersToBePaired.remove(potentialAdversary);
					copyPlayersToBePaired.remove(lowestPlayer); copyPlayersToBePaired.remove(potentialAdversary);
					//DEBUG
					System.out.println("Players to be paired NOT sorted : " + copyPlayersToBePaired);
					System.out.println("Players to be paired SORTED : " + this.playersToBePaired);
					// Reset starting position of shortened playersToBePaired List
					playersToBePairedIterator = this.playersToBePaired.listIterator(this.playersToBePaired.size());
				}
			}
			// A pair is still possible (at least 2 or more players for pairing)
			if(this.playersToBePaired.size() >= 2)
			{
				playersToBePairedIterator.previous();
				lowestPlayer = playersToBePairedIterator.previous();
				starterIndexOfSearch = copyPlayersToBePaired.indexOf(lowestPlayer);
				if(starterIndexOfSearch != 0)					
					playersToBePairedIterator2 = copyPlayersToBePaired.listIterator(starterIndexOfSearch);
				else
					playersToBePairedIterator2 = copyPlayersToBePaired.listIterator(this.playersToBePaired.size());
			}
		}
		notifyObservers("RegisteredPlayersTableModel");
		notifyObservers("GamesTableModel");
		return formedGames;
	}
	
	private boolean checkMinimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther(Player lowestPlayer, Player higherPlayer)
	{
		boolean isSatisfied = false;
		
		/*Collections.sort(games, new Comparator<Game>(){
			@Override
			public int compare(Game g1, Game g2) {
				// Natural ordening with dates <=> less recent to most recent
				// The "-" reverses the natural ordening
				return - (g1.getDate().compareTo(g2.getDate()));
			}
		});*/
		
		// Could be -> g.isGameAgainst(higherPlayer)
		/*if(g.isGameWithPlayers(lowestPlayer, higherPlayer))
		{
			
		}*/
		
		if(lowestPlayer.howManyGamesAgoLastPlayedAgainstPlayer(higherPlayer) >= minimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther
				&& higherPlayer.howManyGamesAgoLastPlayedAgainstPlayer(lowestPlayer) >= minimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther)
			isSatisfied = true;
		return isSatisfied;
	}
	
	private Game formGame(Player lowestRankedPlayer, Player higherRankedPlayer)
	{
		int comparisonWBBalance = lowestRankedPlayer.compareWhiteBlackBalance(higherRankedPlayer);
		
		if(comparisonWBBalance < 0)
			return new Game(gameID++, new Date(), lowestRankedPlayer, higherRankedPlayer);
		if(comparisonWBBalance > 0)
			return new Game(gameID++, new Date(), higherRankedPlayer, lowestRankedPlayer);
		if(comparisonWBBalance == 0)
			return new Game(gameID++, new Date(), lowestRankedPlayer, higherRankedPlayer);	
		
		return null;
	}

	/**
	 * 
	 * @param name
	 * The firstname and the lastname of a new player to be added TO TOURNAMENT
	 */
	public void addPlayers(String names) {
	
	names = names.replaceAll(" *, *", ",");
	System.out.println(names);
	String delimiter = ",";
	String delimter2 = " ";
	String[] namesSplit = names.split(delimiter);
	
		//DEBUG
		for(int i =0; i < namesSplit.length ; i++)
		{
			 namesSplit[i].trim();
			 
			 System.out.println(namesSplit[i]);
			 String[] name = namesSplit[i].split(delimter2, 2);
			 registeredPlayers.add(new Player(name[0], name[1]));
		}	
		notifyObservers("RegisteredPlayersTableModel");
	}
	
	public void savePlayer(String[] nameBefore, String[] nameAfter)
	{
		Player player = findRegisteredPlayer(nameBefore);
		player.setFirstname(nameAfter[0]);
		player.setLastname(nameAfter[1]);
		notifyObservers("RegisteredPlayersTableModel");
	}

	/**
	 * 
	 * @param name
	 * The firstname and the lastname of the player to be removed FROM TOURNAMENT
	 */
	public void removePlayer(String[] name) {
		Player player = findRegisteredPlayer(name);
		registeredPlayers.remove(player);
		notifyObservers("RegisteredPlayersTableModel");
	}
	
	/**
	 * 
	 * @param names
	 * A string containing one or more names.
	 */
	public void addPlayersToBePaired(String names) {
	
	names = names.replaceAll(" *, *", ",");
	System.out.println(names);
	String delimiter = ",";
	String delimter2 = " ";
	String[] namesSplit = names.split(delimiter);
	
		//DEBUG
		for(int i =0; i < namesSplit.length ; i++)
		{
			 namesSplit[i].trim();
			 
			 System.out.println(namesSplit[i]);
			 String[] name = namesSplit[i].split(delimter2);
			 
		}	
		for(int i =0; i < namesSplit.length ; i++)
		{
			 namesSplit[i].trim();
			 
			 System.out.println(namesSplit[i]);
			 String[] name = namesSplit[i].split(delimter2);
			 Player playerToAdd = findRegisteredPlayer(name);
			 Iterator<Player> it = registeredPlayers.iterator();
			 while(it.hasNext())
			 {
				 Player player = it.next();
				 if(player.isSamePlayer(playerToAdd))
				 {
					 playersToBePaired.add(player);
					 it.remove();

					 
				 }
			 }
		}
		notifyObservers("RegisteredPlayersTableModel");
	}
	
	
	/**
	 * 
	 * @param name
	 * The firstname and the lastname of the player to be removed FROM SHUTTLE
	 */
	public void removePlayersToBePaired(String names) {
		names = names.replaceAll(" *, *", ",");
		System.out.println(names);
		String delimiter = ",";
		String delimter2 = " ";
		String[] namesSplit = names.split(delimiter);
		
			//DEBUG
			for(int i =0; i < namesSplit.length ; i++)
			{
				 namesSplit[i].trim();
				 
				 System.out.println(namesSplit[i]);
				 String[] name = namesSplit[i].split(delimter2);
				 Player playerToRemove = findPlayerToBePairedByName(name);
				 Iterator<Player> it = playersToBePaired.iterator();
				 while(it.hasNext())
				 {
					 Player player = it.next();
					 if(player.isSamePlayer(playerToRemove))
					 {
						 it.remove();
						 registeredPlayers.add(player);
					 }
				 }
			}	
			notifyObservers("RegisteredPlayersTableModel");
		}

	/**
	 * 
	 * @param gameID
	 * The ID of the game which was given a (corrected) result
	 * @param result
	 * The (corrected) result itself
	 * @param inProgress 
	 */
	public void saveResult(int gameID, String[] result, boolean inProgress) {
		int[] numericalResult = new int[2];
		numericalResult[0] = Integer.valueOf(result[0]);
		numericalResult[1] = Integer.valueOf(result[1]);
		
		Game game = findGameByID(gameID);
		if(inProgress)
			game.setDate(new Date());
		game.updatePlayerStats(result);
	}

	/**
	 * 
	 * @return
	 * A list of players sorted according to points scored (Player.amountOfPoints) in this tournament
	 */
	public List<Player> getPlayersAccordingToRanking() {		
		//DEBUG
		System.out.println("Players of ranking NOT yet sorted: " + registeredPlayers.toString());
		
		Collections.sort(registeredPlayers, new PlayerComparator());
		
		//DEBUG
		System.out.println("Players of ranking SORTED: " + registeredPlayers.toString());
		
		return registeredPlayers;
	}

	/**
	 * 
	 * @param name
	 * The firstname and the lastname of the player to be found
	 * @return
	 * The found player (based on firstname and lastname)
	 */
	private Player findPlayerToBePairedByName(String[] name) {
		for(Player player : playersToBePaired)
		{
			if(player.getFirstname().equals(name[0]) && player.getLastname().equals(name[1]))
				return player;
		}
		return null;
	}
	private Player findRegisteredPlayer(String[] name)
	{
		for(Player player : registeredPlayers)
		{
			if(player.getFirstname().equals(name[0]) && player.getLastname().equals(name[1]))
				return player;
		}
		return null;
	}
	
	
	
	private Game findGameByID(int gameID)
	{
		for(Game game : games)
		{
			if(game.getGameID() == gameID)
				return game;
		}
		return null;
	}
	
	/**
	 * @param date
	 * The creation date of the new game (a.k.a. the date of pairing up)
	 * @param playerWhite
	 * The white player of the new game (obtained through Tournament.pairUp method)
	 * @param playerBlack
	 * The black player of the new game (obtained through Tournament.pairUp method)
	 */
	//TODO
	private void addGame(/*int gameID,*/ Date date, Player playerWhite, Player playerBlack) {
		Game newGame = new Game((getGameID() + 1), date, playerWhite, playerBlack);
		games.add(newGame);
	}
	
	// GETTERS
	public String getName() {
		return name;
	}

	public List<Player> getPlayersToBePaired() {
		return playersToBePaired;
	}

	public List<Player> getRegisteredPlayers() {
		return registeredPlayers;
	}
	
	

	public List<Game> getGames() {
		return games;
	}

	public int getGameID() {
		return gameID;
	}

	// SETTERS
	public void setDate(Date date) {
		this.date = date;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setPlayersToBePaired(List<Player> playersToBePaired) {
		this.playersToBePaired = playersToBePaired;
	}

	public void setRegisteredPlayers(List<Player> players) {
		this.registeredPlayers = players;
	}

	public void setGameID(int gameID) {
		this.gameID = gameID;
	}

	public void setGames(List<Game> games) {
		this.games = games;
	}
	
	public static void setMinimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther(
			int minimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther) {
		Tournament.minimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther = minimumAmountOfGamesAgo2PlayersLastPlayedAgainstEachOther;
	}

	public static void setMaximumAmountOfPlaces2PlayersApartOfEachOther(
			int maximumAmountOfPlaces2PlayersApartOfEachOther) {
		Tournament.maximumAmountOfPlaces2PlayersApartOfEachOther = maximumAmountOfPlaces2PlayersApartOfEachOther;
	}

	/**
	 * 
	 * @class 
	 * Is being used twice to sort players according to Player.amountOfPoints
	 * 1/ Tournament.getPlayersAccordingToRanking()
	 * 2/ Tournament.pairUp(..)
	 *
	 */
	class PlayerComparator implements Comparator<Player>
	{
		@Override
		public int compare(Player p1, Player p2) {
			return p1.getAmountOfPoints() - p2.getAmountOfPoints();
		}			
	}

	@Override
	public void addObserver(MyObserver o) {
		observers.add(o);
	}

	@Override
	public void notifyObservers(String adressee) {
		for(MyObserver o : observers)
			o.update(adressee);
	}
	
	
}